package fr.umlv.galcon.game;

import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Timer;

import zen2.ApplicationCode;
import zen2.ApplicationContext;
import zen2.KeyboardEvent;
import zen2.MotionEvent;
import fr.umlv.galcon.draw.GalconDraw;
import fr.umlv.galcon.item.GraphicItem;
import fr.umlv.galcon.item.ItemListSingleton;
import fr.umlv.galcon.planet.Planet;
import fr.umlv.galcon.planet.StockTask;
import fr.umlv.galcon.player.Player;
import fr.umlv.galcon.player.PlayerType;
import fr.umlv.galcon.player.ai.ArtificialIntelligence;
import fr.umlv.galcon.ship.SendShip;
import fr.umlv.galcon.ship.ShipProperties;
import fr.umlv.galcon.utils.Utils;

public class Game implements ApplicationCode {

    private final int width;
    private final int heigth;

    private final int nbHuman;
    private final int nbComputer;

    private final int nbPlanets;

    private Player player;

    /**
     * Galaxy's constructor
     * 
     * @param width
     *            : frame width
     * @param heigth
     *            : frame height
     * @param nbHuman
     *            : nb human Player
     * @param nbComputer
     *            : nb cpu Player
     * @param nbPlanets
     *            : nb Planet
     */
    public Game(int width, int heigth, int nbHuman, int nbComputer, int nbPlanets) {
	this.width = width;
	this.heigth = heigth;
	this.nbHuman = nbHuman;
	this.nbComputer = nbComputer;
	this.nbPlanets = nbPlanets;

	createWorld();

    }

    /**
     * Get width of Galaxy
     * 
     * @return galaxy's width
     */
    public int getWidth() {
	return width;
    }

    /**
     * Get height of Galaxy
     * 
     * @return galaxy's height
     */
    public int getHeigth() {
	return heigth;
    }

    /**
     * Get the shape of galaxy
     * 
     * @return galaxy's shape
     */
    public Shape getShape() {
	return new Rectangle2D.Double(0, 0, width, heigth - Utils.INFO_AREA);
    }

    /**
     * Function which create the GalconIG world.
     * 
     * @param nbPlanets
     */
    private void createWorld() {
	Random r = new Random();

	int cpt = 0;
	int idPlayer = 1;
	int nbPlanets = this.nbPlanets;

	for (int i = 0; i < nbHuman; i++) {
	    player = new Player(PlayerType.HUMAN, "Human " + idPlayer, idPlayer);
	    Planet planet = new Planet(r.nextInt(width), r.nextInt(heigth), r.nextInt(150) + 50, player, ShipProperties.WEAK,
		    Utils.PLAYER_SHIP, 1);

	    // Si la plan�te est valide
	    if (isValid(planet, player)) {
		ItemListSingleton.getInstance().addPlanet(planet);
		nbPlanets--;

		GalconManager.addPlayer(player, 1);
		idPlayer++;
	    } else
		i--;
	}

	for (int i = 0; i < nbComputer; i++) {
	    player = new Player(PlayerType.COMPUTER, "CPU " + idPlayer, idPlayer);
	    Planet planet = new Planet(r.nextInt(width), r.nextInt(heigth), r.nextInt(150) + 50, player, ShipProperties.MEDIUM,
		    Utils.PLAYER_SHIP, 1);

	    // Si la planète est valide
	    if (isValid(planet, player)) {
		ItemListSingleton.getInstance().addPlanet(planet);
		nbPlanets--;

		GalconManager.addPlayer(player, 1);
		idPlayer++;
	    } else
		i--;
	}

	idPlayer = 0;
	for (int i = cpt; i < nbPlanets; i++) {
	    Planet planet = new Planet(r.nextInt(width), r.nextInt(heigth), r.nextInt(150) + 50, new Player(PlayerType.NEUTRAL, "",
		    idPlayer), ShipProperties.getShipById(r.nextInt(5)), r.nextInt(Utils.NEUTRAL_SHIP), 1);

	    // Si la planète est valide
	    if (isValid(planet, player)) {
		ItemListSingleton.getInstance().addPlanet(planet);

		GalconManager.addPlayer(player, 1);
	    } else
		i--;
	}
    }

    /**
     * Function which manage the mouse event
     * 
     * @param event
     *            : the MotionEvent to manage
     */
    public void manageMouseEvent(MotionEvent event) {
	if (event == null)
	    return;

	switch (event.getKind()) {
	case ACTION_DOWN:
	    // Boucle sur toutes les planètes
	    for (int i = 0; i < ItemListSingleton.getInstance().getPlanetList().size(); i++) {
		Planet p = ItemListSingleton.getInstance().getPlanetList().get(i);
		// Si la plan�te contient les X/Y
		if (p.contains(new Point2D.Double(event.getX(), event.getY()))) {
		    // Si elle n'est pas deja sélectionner et qu'elle est a un
		    // humain
		    if (!p.getSelectorManager().isSelected() && p.getOwner().getPlayerType() == PlayerType.HUMAN) {
			p.getSelectorManager().setSelected(true);
			ItemListSingleton.getInstance().addClickedPlanet(p);
			break;
		    } else {
			if (!ItemListSingleton.getInstance().getClickedPlanetList().isEmpty()) {
			    for (int j = 0; j < ItemListSingleton.getInstance().getClickedPlanetList().size(); j++) {
				Planet p2 = ItemListSingleton.getInstance().getClickedPlanetList().get(j);
				attack(p2, p);
				ItemListSingleton.getInstance().removeClickedPlanet(p);
				p.getSelectorManager().setSelected(false);
			    }
			}
			ItemListSingleton.getInstance().getClickedPlanetList().clear();
		    }
		}
	    }
	    break;

	case WHEEL_UP:
	    GalconManager.raiseScroll();
	    break;
	case WHEEL_DOWN:
	    GalconManager.reduceScroll();

	default:
	    break;
	}

    }

    /**
     * Function wich manage the keybord event
     * 
     * @param keyEvent
     *            : the KeyBoardEvent to manage
     */
    public void manageKeyEvent(KeyboardEvent keyEvent) {

	if (keyEvent == null)
	    return;

	switch (keyEvent.getKey()) {
	case ALT:
	    GalconManager.raiseScroll();
	    break;

	case CTRL:
	    GalconManager.reduceScroll();
	    break;

	case A:
	    List<Planet> planet = ItemListSingleton.getInstance().getPlanetList();
	    for (Planet p : planet) {
		if (!p.getSelectorManager().isSelected() && p.getOwner().getPlayerType() == PlayerType.HUMAN) {
		    p.getSelectorManager().setSelected(true);
		    ItemListSingleton.getInstance().getClickedPlanetList().add(p);
		}
	    }
	    break;

	case W:
	    changeShypeType(ShipProperties.WEAK, 120, 100);
	    break;

	case M:
	    changeShypeType(ShipProperties.MEDIUM, 100, 100);
	    break;

	case S:
	    changeShypeType(ShipProperties.STRONG, 80, 100);
	    break;
	default:
	    break;
	}
    }

    /**
     * Launch an offensive by sending Ships.
     * 
     * @param attack
     *            The Planet to attacks.
     * @param defends
     *            The Planet which will defend itself.
     */
    private void attack(Planet attack, Planet defends) {
	attack.getSelectorManager().setSelected(false);
	// Calculate the quantity of Ship to send
	int toSend = (int) (GalconManager.getScroll() / 100 * attack.getStock().getRealStock());

	// Set the real stock of the Planet
	attack.getStock().setRealStock(attack.getStock().getRealStock() - toSend);

	// send ships
	SendShip ship = new SendShip(attack, defends, toSend);
	Timer timer = new Timer();
	timer.schedule(ship, 0, 200);
    }

    /**
     * Function which test if the planet doesn't intersect an other planet, and
     * test if the planet is contains in the galaxy bound.
     * 
     * @param planet
     *            : planet to test if it is valid
     * @param player
     *            : planet' owner
     * @return true if the planet is valid else false
     */
    private boolean isValid(Planet planet, Player player) {

	// Test su la planète en intesecte une autre
	for (GraphicItem p : ItemListSingleton.getInstance().getPlanetList()) {
	    if (planet.intersect(p)) {
		return false;
	    }

	}

	if (!getShape().contains(planet.getShape().getBounds2D()))
	    return false;

	return true;

    }

    private void changeShypeType(ShipProperties properties, int x, int y) {
	List<Planet> planetToWeak = ItemListSingleton.getInstance().getPlanetList();
	for (Planet p : planetToWeak) {
	    if (p.getSelectorManager().isSelected() && p.getOwner().getPlayerType() == PlayerType.HUMAN
		    && p.getShipProperties() != properties && p.getShipProperties() != ShipProperties.NEUTRAL) {
		p.getStock().setRealStock(p.getStock().getRealStock() * x / y);
		p.getStock().setShipStock(p.getStock().getRealStock() * x / y);
		p.setShipProperties(properties);
	    }
	}
    }

    @Override
    public void run(ApplicationContext context) {
	GalconDraw galconDraw = new GalconDraw();

	Timer aiTimer = new Timer();
	for (Map.Entry<Player, Integer> player : GalconManager.getPlayers().entrySet()) {
	    if (player.getKey().getPlayerType() == PlayerType.COMPUTER)
		aiTimer.schedule(new ArtificialIntelligence(100, player.getKey()), 100, 400);
	}

	final Timer timer = new Timer();
	timer.schedule(new StockTask(), 0, 500);

	while (true) {
	    if (!GalconManager.getEndGame()) {
		MotionEvent event = context.pollMotion();
		manageMouseEvent(event);
		KeyboardEvent keyEvent = context.pollKeyboard();
		manageKeyEvent(keyEvent);

	    }

	    try {
		Thread.sleep(25L);
	    } catch (InterruptedException e) {
		Thread.currentThread().interrupt();
	    }
	    context.render(galconDraw);

	}

    }

}
